#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include <iomanip>

using namespace std;

const long long GCOV_DATA_MAGIC = 0x67636461u;
const long long GCOV_TAG_FUNCTION = 0x01000000u;
const long long GCOV_TAG_COUNTER_BASE = 0x01a10000u;
const long long GCOV_TAG_OBJECT_SUMMARY = 0xa1000000u;

/* value profile counter */
const long long GCOV_TAG_COUNTER_INTERVAL = 0x01a30000u;
const long long GCOV_TAG_COUNTER_POW2 = 0x01a50000u;
const long long GCOV_TAG_COUNTER_TOPN = 0x01a70000u;
const long long GCOV_TAG_COUNTER_IC = 0x01a90000u;
const long long GCOV_TAG_COUNTER_AVERAGE = 0x01ab0000u;
const long long GCOV_TAG_COUNTER_IOR = 0x01ad0000u;
const long long GCOV_TAG_COUNTER_TP = 0x01af0000u;

static int ReadFile (const string fileName, vector<char>& out)
{
    ifstream file(fileName, ios::binary);
    if (!file.is_open())
    {
        cout << "Can not open file. Please check filename please. " << endl;
        exit(1);
    }
    streamsize bufSize = 4096;
    char buf;
    streamsize byteread;;
    while (!file.eof())
    {
        file.get(buf);
        out.push_back(buf);
    }
    return 0;
}

static void SplitLines(const vector<char>& in, vector<string>& out)
{
    int start = 0;
    int size = in.size();
    for (int i = 0; i < size; i++)
    {
        if (in[i] != '\r' && in[i] != '\n')
        {
            continue;
        }
        out.push_back(string(in.begin() + start, in.begin() + i));
        if (in[i] == '\r' && i < size && in[i + 1] == '\n')
        {
            i++;
        }
        start = i + 1;
    }
    if (start < size)
    {
        out.push_back(string(string(in.begin() + start, in.end())));
    }
}

static int WriteFile (string fileName, const vector<char> in, unsigned int runMax)
{
    if (in.size() < 16)
    {
        fprintf(stderr, "[!] Not enough size to write\n");
        return 1;
    }
    ofstream file (fileName, ios::binary);
    if (!file.is_open())
    {
        cout << "Can not open file. Please check filename please. " << endl;
        exit(1);
    }
    file.write(in.data(), 16);
    
    unsigned int title[4] = {
        GCOV_TAG_OBJECT_SUMMARY,
        8,
        4,
        runMax
    };
    file.write(reinterpret_cast<const char*>(title), sizeof(title));
    file.write(in.data() + 16, in.size() - 16);
    file.close();
    return 1;
}

static int ProcessFile(const string fileName)
{
    vector<char> source;
    if (ReadFile(fileName, source))
    {
        fprintf(stderr, "[!] Fail to read file: %s \n", fileName.c_str());
        return 1;
    }

    int state = 1;
    unsigned int valMax = 0;
    unsigned int count = 0;
    unsigned int n = source.size();
    auto vData = (const unsigned int*) source.data();
    for (int i = 0; i < n; ++i)
    {
        unsigned int val = vData[i];
        switch (state)
        {
            case 1:
                if (val != GCOV_DATA_MAGIC)
                {
                    fprintf(stderr, "[!] GCOV_DATA_MAGIC mismatches: 0x%x\n", val);
                    return 1;
                } 
                i += 3;
                state = 2;
                break;

            case 2:
                if (i == n && val) {
                    fprintf(stderr, "[!] Single last tag: 0x%x\n", val);
                    return 1;
                }
                if (val == GCOV_TAG_FUNCTION) {
                    i += 1 + (vData[i + 1] / 4);
                } else if (val == GCOV_TAG_COUNTER_BASE) {
                    if (vData[i + 1] % 2) {
                        fprintf(stderr, "[!] Invalid length: %d\n", vData[i + 1] / 4);
                        return 1;
                    }
                    count = vData[++i];
                    cout << count << endl;
                    if (count) {
                        state = 3;
                    }
                } else if (val) {
                    switch (val) {
                        case GCOV_TAG_COUNTER_INTERVAL:
                        case GCOV_TAG_COUNTER_POW2:
                        case GCOV_TAG_COUNTER_TOPN:
                        case GCOV_TAG_COUNTER_IC:
                        case GCOV_TAG_COUNTER_AVERAGE:
                        case GCOV_TAG_COUNTER_IOR:
                        case GCOV_TAG_COUNTER_TP:
                            i += 1 + (vData[i + 1] / 4);
                            break;
                        default:
                            fprintf(stderr, "[!] Unknown tag: 0x%x\n", val);
                            return 1;
                    }
                }
                break;

            case 3:
                valMax = valMax < val ? val : valMax;
                // cout << "Current value should be " << val << endl;
                if (--count == 0) {
                    state = 2;
                }
                break;
            default:
                break;
        }
    }
    if (WriteFile(fileName, source, valMax)) {
        return 1;
    }
    return 1;
}

int main(int argc, char** argv)
{
    const string filename = "spinlock.gcda";
    ProcessFile(filename);
    
    return 0;
}